using System;
using System.IO;
using System.Diagnostics;
using System.Reflection;
using System.Text;

namespace PIM.Utilities.Logging
{
	/// <summary>
	/// A simple class to log messgaes to a text file
	/// </summary>
	public class QSLogger : IDisposable
	{
     //   static string insertAppLog = "INSERT INTO APPLICATIONLOG (LOGID, APPLICATION ,LEVELS ,TYPE ,MODULE ,DESCRIPTION ,MESSAGE ,ACTIONREQUIRED ,ACTIONOWNER ,ACTIONSEVERITY ,CREATEDBY ,CREATORTYPE ,CREATEDON ,OBJECTNAME ,OBJECTID ,INFO1 ,INFO2) VALUES ( {0}, N'{1}' ,N'{2}' ,N'{3}' ,N'{4}' ,N'{5}' ,N'{6}', {7} ,{8} ,N'{9}' ,{10}, {11} ,{12} ,N'{13}' ,N'{14}' ,N'{15}',N'{16}')";

		public enum Level
		{
			Error = 0,
			Warning = 1,
			Info = 2,
			Trace = 3
		};

		private Level _currentlevel = Level.Warning;
		private object _lockObject = new object();
		private bool _logEnabled = true;
		private string _fileName;
		private string _filePath;

        #region Archiving of Log Files

        private static volatile bool _hasLogMaxSizeSet = false;
        private static long _maxLogFileSize = 10485760; //10 * Math.Pow(2, 10); // 10 MB
        private static string _logArchivePath;
        private static object _lockObjectForLogFileSize = new object();

        public const string MAXLOGFILESIZE = "MAXLOGFILESIZE";
        public const string LOGARCHIVEPATH = "LOGARCHIVEPATH";

        /// <summary>
        /// reads the MaxLogFileSize & LogArchivePath from configuration and sets it
        /// </summary>
        private void SetMaxLogFileSize()
        {
            if (!_hasLogMaxSizeSet)
            {
                lock (_lockObjectForLogFileSize)
                {
                    if (!_hasLogMaxSizeSet)
                    {
                        try
                        {
                            QSecureConfig config = new QSecureConfig();
                            string temp = config.GetValue(MAXLOGFILESIZE); // in KBs
                            if (!String.IsNullOrEmpty(temp))
                            {
                                _maxLogFileSize = Convert.ToInt64(temp) * 1024; // converting kbs into bytes
                            }

                            temp = config.GetValue(LOGARCHIVEPATH);
                            if (!String.IsNullOrEmpty(temp))
                            {
                                _logArchivePath = temp;
                                if (!Directory.Exists(_logArchivePath))
                                    Directory.CreateDirectory(_logArchivePath);
                            }
                            else
                            {
                                _logArchivePath = _filePath;
                            }

                            _hasLogMaxSizeSet = true;
                        }
                        catch
                        {
                        }
                    } // inner if (! _hasLogMaxSizeSet)
                } //lock     
            } // outer if (! _hasLogMaxSizeSet)
        }

        private void ArchiveLog()
        {
            try
            {

                SetMaxLogFileSize();

                string fileFullName = Path.Combine(_filePath, _fileName);
                long length = 0;

                using (FileStream logFS = File.OpenRead(fileFullName))
                {
                    length = logFS.Length;
                }

                if (length > _maxLogFileSize)
                {
                    string newFileName = String.Format("{0}_{1}.log",
                        QSecureHelper.SplitString(_fileName, ".", true)[0],
                        DateTime.Now.Ticks);
                    string newFileFullName = Path.Combine(_logArchivePath, newFileName);

                    //moving the file
                    File.Move(fileFullName, newFileFullName);                                       
                }                
                
            }
            catch (Exception ex)
            {
                LogToEventLog(String.Format("Archiving of log file {0} failed with message {1}. Stack trace {2} ",
                    _fileName, ex.Message, ex.StackTrace));
            }
        } 

        #endregion


		public QSLogger() : this("Log.txt")
		{
		}

		public QSLogger(string filename) : this(filename, GetStandardLogPath()) 
		{
		}

		public QSLogger(string filename, string filePath)
		{
			_fileName = filename;
			_filePath = filePath;

            try
            {
                if (!Directory.Exists(_filePath))
                    Directory.CreateDirectory(_filePath);

                //Create an empty file if not found
                if (!File.Exists(Path.Combine(_filePath, _fileName)))
                    using (StreamWriter _logWriter = File.AppendText(Path.Combine(_filePath, _fileName)))
                    {
                    }
            }
            catch { }
		}

		public void LogError(string msg, params object [] args)
		{
			StackTrace callStack = new StackTrace();
			Log(callStack.GetFrame(1).GetMethod(), Level.Error, msg, args);
		}
		
		public void LogWarning(string msg, params object [] args)
		{
			StackTrace callStack = new StackTrace();
			Log(callStack.GetFrame(1).GetMethod(), Level.Warning, msg, args);
		}

		public void LogInfo(string msg, params object [] args)
		{
			StackTrace callStack = new StackTrace();
			Log(callStack.GetFrame(1).GetMethod(), Level.Info, msg, args);
		}
		
		public void LogTrace(string msg, params object [] args)
		{
			StackTrace callStack = new StackTrace();
			Log(callStack.GetFrame(1).GetMethod(), Level.Trace, msg, args);
		}

		public Level CurrentLevel
		{
			get { return _currentlevel; }
			set { _currentlevel = value; }
		}

		public bool EnableLogging
		{
			get { return _logEnabled; }
			set { _logEnabled = value; }
		}

		public void SetCurrentLevel(string level)
		{
			if ( level != null && level != "" && level != string.Empty )
				_currentlevel = (Level)Enum.Parse(typeof(Level), level, true);
		}

		public static string GetStandardLogPath()
		{
			try
			{
				QSecureConfig cfg = new QSecureConfig();
				string path = cfg.GetValue("LOGPATH");
                if (path.Contains(":") || path.Contains(@"\\"))
                { // if this is a absolute path; the path will contain a : - for eg., C:\Program Files\...
                    return path;
                }
                else
                { //if it is a relative path. We assume the path is relative to basedirectory. 
                    string fullpath = System.IO.Path.GetFullPath(System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, path));
                    return fullpath;
                }
			}
			catch
			{
                try
                {
                    return Path.GetTempPath();
                }
                catch (Exception ex)
                {
                    return ".";
                }
			}
		}

		#region IDisposable Members

		public void Dispose()
		{
			// TODO:  Add QSLogger.Dispose implementation
		}

		#endregion
        //Function to add log to Database -- added by BK
        public  void Log(QSLogConfiguration logOption, QSLogMessage logMessage)
        {
            int LogTo = 0; // Presently only logto DB
            try
            {
                switch (LogTo)
                {
                    case 0:
                        //check for LogLevel Setting
                        int ilevel = Convert.ToInt32(logMessage.Level);
                        if ((ilevel <= (int)logOption.LogLevel ) && ((int)logOption.LogLevel < 4))
                            LogToDatabase(logOption, logMessage);
                        break;
                    case 1:
                        LogToEventLog();
                        break;
                    case 2:
                        LogToFile();
                        break;
                    default:
                        LogToDatabase(logOption, logMessage);
                        break;
                }
            }
            catch(Exception e)
            {
                String MsgToLog = string.Format("QSLogger fails to log error - {0}",e.Message) ;
                EventLog ErrLog = new EventLog();
                ErrLog.Source = "QSLogger";
                //ErrLog.WriteEntry(MsgToLog, EventLogEntryType.Error, 1, 1);
            }
        }
		#region Private
		private void Log(MethodBase method, Level level, string msg, params object[] args)
		{
			try
			{
				if ((int)_currentlevel >= (int)level && _logEnabled )
				{
					lock (_lockObject)
					{                       
                        //archiving the log file
                        ArchiveLog();
                   
						using (StreamWriter _logWriter = File.AppendText(Path.Combine(_filePath, _fileName)))
						{
						
							DateTime now = DateTime.Now;
							_logWriter.WriteLine("{0}|{1}|{2,-8}|{3, -10}|{4,-15}|{5,-15}|{6,-15}|{7}", 
							                     now.ToShortDateString(), 
							                     now.ToString("HH:mm:ss.ffff"), 
							                     level.ToString(),
                                                 System.Diagnostics.Process.GetCurrentProcess().Id,
							                     method.DeclaringType.Module.Name,
							                     method.DeclaringType.Name, method.Name,
							                     string.Format(msg, args));

							_logWriter.Flush();
						}                        
					}
				}
			}
			catch
			{
				//If log itself fails, nothing to do
			}
		}
                
        //Log Error details to database - added by bk
        private void LogToDatabase(QSLogConfiguration logOption, QSLogMessage logMessage)
        {
            string type = string.Empty;
            PIM.Utilities.DataAccess.PIMManager objDbMgr;
            objDbMgr = logOption.QSDBManager;
            QSQueryBuilderInsert query = new QSQueryBuilderInsert(objDbMgr.DBPROVIDER, "APPLICATIONLOG");
            //Private local veriables to hold values.
            int ID = objDbMgr.GetNextObjectID("ApplicationLog");
            query.AddColumnData("LOGID", ID, DATATYPE.NUMBER, null);
            query.AddColumnData("APPLICATION", logMessage.Application,DATATYPE.TEXT, null);
            query.AddColumnData("LEVELS", logMessage.Level, DATATYPE.TEXT, null);
            if (logMessage.Type == QSLogMessage.ExceptionType.ServiceException)
                type = "SystemError";
            else
                type = "BusinessException";
            query.AddColumnData("TYPE", type, DATATYPE.TEXT, null);
            query.AddColumnData("MODULE",logMessage.Module, DATATYPE.TEXT, null);
            query.AddColumnData("DESCRIPTION", 
                Trim(logMessage.Description, 2000 / (objDbMgr.DBPROVIDER == DBProvider.Oracle? 2 :1)),
                DATATYPE.TEXT, null);
            query.AddColumnData("MESSAGE",
                Trim(logMessage.Message, 4000 / (objDbMgr.DBPROVIDER == DBProvider.Oracle ? 2 : 1)), 
                DATATYPE.TEXT, null);
            query.AddColumnData("ACTIONREQUIRED", "0", DATATYPE.NUMBER, null);
            query.AddColumnData("ACTIONOWNER", logMessage.ActionOwner, DATATYPE.NUMBER, null);
            query.AddColumnData("ACTIONSEVERITY", logMessage.ActionSeverity, DATATYPE.TEXT, null);
            query.AddColumnData("CREATEDBY", logMessage.UserID, DATATYPE.NUMBER, null);
            query.AddColumnData("CREATORTYPE", logMessage.UserType, DATATYPE.NUMBER, null);
            query.AddColumnData("CREATEDON", logMessage.Creationdate, DATATYPE.DATETIME, null);
            query.AddColumnData("OBJECTNAME", logMessage.ObjectName, DATATYPE.TEXT, null);
            query.AddColumnData("OBJECTID", logMessage.ObjectID, DATATYPE.TEXT, null);
            query.AddColumnData("INFO1", logMessage.Info1, DATATYPE.IMAGE, null);
            query.AddColumnData("INFO2", logMessage.Info2, DATATYPE.IMAGE, null);
            string strQuery = string.Empty;
            try
            {
                strQuery = query.GetQueryText(null);
                objDbMgr.Execute(strQuery, query.Params);
            }
            catch (Exception ex)
            {
                LogToEventLog(
                    String.Format("Actual Message Logged: {0} \n\n\n Exception thrown while logging: {1}",
                    logMessage == null ? string.Empty : logMessage.Message,
                    ex.ToString()));
            }
        }

        private static string Trim(string input, int length)
        {
            if (string.IsNullOrEmpty(input)) return string.Empty;

            if (input.Length > length)
            {
                input = input.Substring(0, length);
            }

            return input;
        }

        private void LogToEventLog()
        {
            LogToEventLog(string.Empty);
        }

        private void LogToEventLog(string msg)
        {
            try
            {
                
                String MsgToLog = String.Format("QSLogger fails to log the error! \n {0}", msg);
                EventLog ErrLog = new EventLog();
                ErrLog.Source = "QSLogger";
                ErrLog.WriteEntry(MsgToLog, EventLogEntryType.Error, 1, 1);
                
            }
            catch (Exception e)
            {
                try
                {
                    LogError("Unable to Log to database. Error is {0} ", e.Message);
                }
                catch (Exception ex) { }
            }
        }
        private static void LogToFile()
        {
            //Log();
        }
        private static int getLevel(string Level)
        {
            int level = 0;
            switch (Level)
            {
                case "Debug":
                    level = 0;
                    break;
                case "Information":
                    level = 1;
                    break;
                case "Warning":
                    level = 2;
                    break;
                case "Error":
                    level = 3;
                    break;
                case "None":
                    level = 4;
                    break;

            }
            return level;
        }
		#endregion

	}


	//A singleton logger that can be initialized and used by all module
	//with in a process for loggin into a common place
	public class GlobalLog
	{
		private static QSLogger	 gLogger = new QSLogger();

		public static QSLogger Logger
		{
			get { return gLogger;}
		}


		public static QSLogger Set(string filename)
		{
			gLogger.Dispose();
			return (gLogger = new QSLogger(filename));
		}

		public static QSLogger Set(string filename, string filePath)
		{
			gLogger.Dispose();
			return (gLogger = new QSLogger(filename, filePath));
		}

		public static QSLogger Set(QSLogger logger)
		{
			gLogger.Dispose();
			return (gLogger = logger);
		}
	}
}
